/*
 * Galaxium Messenger
 * Copyright (C) 2005-2007 Philippe Durand <draekz@gmail.com>
 * Copyright (C) 2005-2007 Ben Motmans <ben.motmans@gmail.com>
 * Copyright (C) 2008 Paul Burton <paulburton89@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Collections.Generic;
using System.Web.Services.Protocols;
using System.Xml;

using Anculus.Core;

using Galaxium.Core;
using Galaxium.Gui;
using Galaxium.Protocol.Msn.Soap;
using Galaxium.Protocol.Msn.Soap.BodyParts;

namespace Galaxium.Protocol.Msn
{
	public sealed class MsnAccount : AbstractAccount, IMsnEntity
	{
		bool _allowUnknownContacts;
		bool _useHTTP = false;
		string _nsHostname = MsnConstants.DefaultNotificationServerHostname;
		int _nsPort = MsnConstants.DefaultNotificationServerPort;
		string _currentMedia;
		string _displayImageContext = null;
		
		internal long _contactID;
		
		internal Soap.BodyParts.Profile _profile = new Soap.BodyParts.Profile ();
		uint _profileTextUpdateTimer;
		
		public string CurrentMedia
		{
			get { return _currentMedia; }
			set
			{
				if ((Session != null) && (Session.Connection != null))
				{
					Session.Connection.Send (new UUXCommand (Session, DisplayMessage, value), delegate
					{
						_currentMedia = value;
					});
				}
				else
					_currentMedia = value;
			}
		}
		
		public override IDisplayImage DisplayImage
		{
			get 
			{
				if ((!string.IsNullOrEmpty (_displayImageContext)) && (base.DisplayImage == null))
					return new MsnDisplayImage (_displayImageContext);
				
				return base.DisplayImage;
			}
			set
			{
				if (DisplayImage == value)
					return;
				
				if ((DisplayImage is MsnDisplayImage) && (value is MsnDisplayImage) && (((MsnDisplayImage)DisplayImage).Sha == ((MsnDisplayImage)value).Sha))
					return;
				
				base.DisplayImage = value;
				
				if ((Session != null) && (Session.Connection != null))
				{
					Session.Connection.Send (new CHGCommand (Session, Presence, MsnClientIdentifier.Default, value as MsnDisplayImage), new CommandHandler (delegate
					{
						ProfileHandle handle = new ProfileHandle ();
						handle.Alias = new ProfileAlias (_contactID.ToString (), "MyCidStuff");
						handle.RelationshipName = "/UserTiles";
						
						RemoveCurrentImage (handle, delegate
						{
							ProfileHandle expressionHandle = new ProfileHandle ();
							expressionHandle.ResourceID = _profile.ExpressionProfile.ResourceID;
							
							RemoveCurrentImage (expressionHandle, delegate
							{
								StoreDisplayImage (handle);
							});
						});
					}));
				}
				else
					base.DisplayImage = value;
			}
		}
		
		// We can't set the DisplayImage directly when loading the account
		// because we don't have a session until later...
		// 
		// So we can store the context here and load it when the session is set
		public string DisplayImageContext
		{
			get { return string.IsNullOrEmpty (_displayImageContext) ? (base.DisplayImage != null ? (base.DisplayImage as MsnDisplayImage).Context : string.Empty) : _displayImageContext; }
			set { _displayImageContext = value; }
		}
		
		public override string DisplayMessage
		{
			get { return base.DisplayMessage; }
			set
			{
				if (value == base.DisplayMessage)
					return;
				
				base.DisplayMessage = value;
				
				if ((Session != null) && (Session.Connection != null))
				{
					Session.Connection.Send (new UUXCommand (Session, value, CurrentMedia), delegate
					{
						if (DisplayMessage != _profile.ExpressionProfile.PersonalStatus)
							ScheduleProfileTextUpdate ();
					});
				}
			}
		}
		
		public override string DisplayName
		{
			get { return base.DisplayName; }
			set
			{
				if (value == base.DisplayName)
					return;
				
				base.DisplayName = value;
				
				if ((Session != null) && (Session.Connection != null))
				{
					Session.Connection.Send (new PRPCommand (Session, value), delegate
					{
						if (DisplayName != _profile.ExpressionProfile.DisplayName)
							ScheduleProfileTextUpdate ();
						
						ABContact abContact = new ABContact ();
						abContact.Info.Type = ContactType.Me;
						abContact.Info.TypeSpecified = true;
						abContact.Info.DisplayName = value;
						abContact.InfoSpecified = true;
						abContact.PropertiesChanged = "DisplayName";
						
						ABContactCollection abContacts = new ABContactCollection ();
						abContacts.Add (abContact);
						
						Session.ABService.BeginABContactUpdate (new Guid (), abContacts, null, null);
					});
				}
			}
		}
		
		public override IPresence Presence
		{
			get { return base.Presence; }
			set
			{
				if ((Session != null) && (Session.Connection != null))
				{
					Session.Connection.Send (new CHGCommand (Session, value, MsnClientIdentifier.Default, DisplayImage as MsnDisplayImage));
				}
				else
					base.Presence = value;
			}
		}
		
		public override IProtocol Protocol
		{
			get { return MsnProtocol.Instance; }
		}
		
		public new MsnSession Session
		{
			get { return base.Session as MsnSession; }
		}
		
		public bool UseHTTP
		{
			get { return _useHTTP; }
			set { _useHTTP = value; }
		}
		
		public string NotificationServerHostname
		{
			get { return this._nsHostname; }
			set { _nsHostname = value; }
		}
		
		public int NotificationServerPort
		{
			get { return this._nsPort; }
			set { _nsPort = value; }
		}
		
		public bool AllowUnknownContacts
		{
			get { return _allowUnknownContacts; }
			set { _allowUnknownContacts = value; }
		}
		
		public Network Network
		{
			get { return Network.WindowsLive; }
		}
		
		public MsnAccount (string uid) : base (null, uid)
		{
		}

		public MsnAccount (string uid, string password, string displayName, bool autoConnect, bool rememberPassword)
			: base (null, uid, password, displayName, autoConnect, rememberPassword)
		{
		}
		
		internal void SetPresence (IPresence presence)
		{
			base.Presence = presence;
		}
		
		protected override void OnSessionChange ()
		{
			base.OnSessionChange ();
			
			_profile = new Profile ();
			
			if (!string.IsNullOrEmpty(_displayImageContext))
			{
				base.DisplayImage = MsnObject.Load (Session, _displayImageContext) as MsnDisplayImage;
				_displayImageContext = null;
			}
		}
		
		void ScheduleProfileTextUpdate ()
		{
			// If we don't have our profile yet then we can't update it
			if (string.IsNullOrEmpty (_profile.ResourceID))
				return;
			
			if (_profileTextUpdateTimer != 0)
				TimerUtility.ResetCallback (_profileTextUpdateTimer);
			else
			{
				_profileTextUpdateTimer = TimerUtility.RequestCallback (delegate
				{
					_profileTextUpdateTimer = 0;
					
					Profile update = new Profile ();
					update.ResourceID = _profile.ResourceID;
					update.ExpressionProfile.FreeText = "Update";
					update.ExpressionProfile.DisplayName = DisplayName;
					update.ExpressionProfile.PersonalStatus = DisplayMessage;
					update.ExpressionProfile.Flags = 0;
					
					Session.StoreService.BeginUpdateProfile (update, delegate (IAsyncResult asyncResult)
					{
						try
						{
							Session.StoreService.EndUpdateProfile (asyncResult);
							Log.Debug ("Roaming profile successfully updated");
						}
						catch (SoapException ex)
						{
							Log.Error ("Error updating roaming profile: {0} {1}", ex.Code.Name, ex.Message);
						}
						catch (Exception ex)
						{
							Log.Error (ex, "Error updating roaming profile");
						}
					}, null);
				}, 2000);
			}
		}
		
		void RemoveCurrentImage (ProfileHandle handle, VoidDelegate callback)
		{
			if ((_profile == null) || (_profile.ExpressionProfile == null) || (_profile.ExpressionProfile.Photo == null))
			{
				callback ();
				return;
			}
			
			Session.StoreService.BeginDeleteRelationships (handle, new ObjectHandle[] { new ObjectHandle (_profile.ExpressionProfile.Photo.ResourceID) }, delegate (IAsyncResult asyncResult)
			{
				try
				{
					Session.StoreService.EndDeleteRelationships (asyncResult);
					Log.Debug ("Successfully deleted display image relationship");
					
					callback ();
				}
				catch (SoapException ex)
				{
					if (MsnXmlUtility.FindText (ex.Detail as XmlElement, "errorcode") == "ItemDoesNotExist")
					{
						// The photo didn't exist, we should continue anyway
						
						Log.Debug ("Roaming profile photo didn't exist");

						callback ();
						
						return;
					}
					
					Log.Error ("Error deleting current display image relationship: {0} {1}", ex.Code.Name, ex.Message);
				}
				catch (Exception ex)
				{
					Log.Error (ex, "Error deleting current display image relationship");
				}
			}, null);
		}
		
		void StoreDisplayImage (ProfileHandle handle)
		{
			Photo photo = new Photo ();
			photo.Name = "GalaxiumDisplayImage";
			
			PhotoStream stream = new PhotoStream ();
			stream.Type = DocumentStreamType.UserTileStatic;
			stream.MimeType = "png"; //TODO: get real mime type
			stream.Data = DisplayImage.ImageBuffer;
			stream.DataSize = 0;
			
			photo.DocumentStreams.Add (stream);
			
			Session.StoreService.BeginCreateDocument (handle, photo, "Messenger User Tile", delegate (IAsyncResult asyncResult)
			{
				try
				{
					photo.ResourceID = Session.StoreService.EndCreateDocument (asyncResult);
					
					Log.Debug ("Successfully uploaded display image to roaming profile");
					
					Relationship relationship = new Relationship ();
					relationship.SourceID = _profile.ExpressionProfile.ResourceID;
					relationship.SourceType = SourceTypeEnum.SubProfile;
					relationship.TargetID = photo.ResourceID;
					relationship.TargetType = DocumentItemType.Photo;
					relationship.RelationshipName = "ProfilePhoto";
					
					Session.StoreService.BeginCreateRelationships (new Relationship[] { relationship }, delegate (IAsyncResult asyncResult2)
					{
						try
						{
							Session.StoreService.EndCreateRelationships (asyncResult2);
							
							Log.Debug ("Successfully related display image to roaming profile");
						}
						catch (SoapException ex)
						{
							Log.Error ("Error relating image to roaming profile: {0} {1}", ex.Code.Name, ex.Message);
						}
						catch (Exception ex)
						{
							Log.Error (ex, "Error relating image to roaming profile");
						}
					}, null);
				}
				catch (SoapException ex)
				{
					Log.Error ("Error uploading image to roaming profile: {0} {1}", ex.Code.Name, ex.Message);
				}
				catch (Exception ex)
				{
					Log.Error (ex, "Error uploading image to roaming profile");
				}
			}, null);
		}
	}
}